define(['knockout', 'util', 'config'], function (ko, Util, Config) {
    function Model(/*{isKo: true, default:{}}*/params) {
        //the params contains the config
        if (!params) params = {};

        $.extend(this, {
            extraData: {},//the data will mix with the model when in ajax request
            ajaxWithQuery: params.ajaxWithQuery//a boolean field to mark request url with query parameters
        });

        var scope = this;

        //the store need get url form a model instance when the store url not assigned
        var defaultMedhods = {Get: 'Get', Post: 'Post', Put: 'Put', Delete: 'Delete'};

        if (scope.proxy) {
            scope.proxy.type = scope.proxy.type ? scope.proxy.type : Config.store.proxyType;
            $.extend(defaultMedhods, scope.proxy.methods);
            scope.proxy.methods = defaultMedhods;
        } else {
            scope.proxy = {type: 'rest', url: '', methods: defaultMedhods};
        }

        if (!scope.fields) scope.fields = [];
        //skip init logic when skipInit is true
        if (!params.skipInit) {
            if (!scope.preSave) scope.preSave = function () {
                return true
            };

            scope.fields.forEach(function (field) {
                //add this property to store server check data
                field._original = JSON.stringify(field);
                field.serverCheck = {success: true, message: ''};

                if (field.type == undefined) {
                    field.type = 'text';
                }

                if (field.defaultValue == undefined) {
                    switch (field.type) {
                        case 'number':
                            field.defaultValue = NaN;
                            break;
                        case 'select':
                            if (field.multiple) {
                                field.defaultValue = [];
                            } else {
                                field.defaultValue = undefined;
                            }
                            break;
                        case 'file':
                            field.defaultValue = {fileArray: [], file: null};
                            break;
                        case 'checkboxes':
                            field.defaultValue = [];
                            break;
                        case 'date':
                            field.defaultValue = (new Date()).pattern('yyyy-MM-dd');
                            break;
                        case 'checkbox':
                            field.defaultValue = false;
                            break;
                        default:
                            field.defaultValue = '';
                    }
                }

                field._attr = {};

                if (field.type == 'select' || field.type == 'checkboxes' || field.type == 'radios') {
                    if (!field.options) field.options = ko.observableArray();
                    if (!field.optionsText) field.optionsText = 'text';
                    if (!field.optionsValue) field.optionsValue = 'value'

                    field._attr['multiple'] = field.multiple ? true : false;
                }

                if(field.type == 'select' || field.type == 'file') {
                    field._attr['multiple'] = field.multiple ? true : false;
                }

                if (field.type == 'file') {
                    field._attr.placeholder = '未选择文件';

                    if (field.accept) {
                        field._attr['accept'] = field.accept;

                        if (field.accept.indexOf('image') > -1) {
                            field._mimeType = 'img';
                        }
                    }

                    if (field.preview) {
                        scope['_preview_' + field.name] = ko.observable('');
                    } else {
                        field.preview = false;
                    }

                    if (!field.maxSize) field.maxSize = 100 * 1024 * 1024;//100MB
                } else {
                    field._attr['placeholder'] = '请输入{0}'.formatTpl(field.alias);
                }

                field.validateMessage = ko.observable();

                if (field.validator && field.validator.length > 0) {
                    field._validator = [];
                    field.validator.forEach(function (v) {
                        var name = v.name ? v.name : v;
                        switch (name) {
                            case 'required':
                                field._validator.push({name: name, message: '必须填写'});
                                field._attr[v] = true;
                                break;
                            case 'email':
                                field._attr.type = 'email';
                                field._validator.push({name: name, message: '邮箱错误'});
                                //field._attr.pattern = '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$';
                                break;
                            case 'mobile':
                                field._validator.push({name: name, message: '手机错误'});
                                field._attr.pattern = '^1(3|4|5|7|8)\\d{9}$';
                                break;
                            case 'number':
                                var min = v.min ? v.min : 0;
                                var max = v.max ? v.max : 10000;

                                field.type = field._attr.type = 'number';
                                field._validator.push({name: name, message: '范围 {0} - {1}'.formatTpl(min, max)});
                                field._attr.pattern = v.step ? '[0-9\\.]*' : '[0-9]*';
                                field._attr.step = v.step;
                                field._attr['min'] = min;
                                field._attr['max'] = max;

                                if (v.step) {
                                    field._attr._fixed = v.step.toString().split('.')[1].length;
                                }
                                break;
                            case 'length':
                                var min = v.min ? v.min : 0;
                                var max = v.max ? v.max : 20;
                                field._validator.push({name: name, message: '长度 {0} - {1}'.formatTpl(min, max)});
                                field._attr.pattern = '^.{{0},{1}}$'.formatTpl(min, max);
                                break;
                        }
                    });

                    field.validateMessage(field._validator[0].message);
                }

                if (!field._attr['type']) field._attr['type'] = field.type;

                field._id = '{0}_{1}'.formatTpl(field.name, Util.uuid(8, 16));
                field._attr['id'] = field._id;
            });
        }

        scope.fields.push({name: 'rowCheck', alias: '选择'});
        scope.fields.push({name: 'id', alias: '编号', defaultValue: Config.modelDefaultId});
        scope.fields.push({name: 'rowId', alias: '行号'});

        if (params && params.isKo) {
            var obj = {};

            scope.fields.forEach(function (field) {
                if (field.name != 'rowId' && field.name != 'rowCheck') {//the rowId and rowCheck no default value
                    obj[field.name] = field.defaultValue;
                }
            });
            //init the value from default obj in params
            if (params.default && params.default instanceof Object) {
                for (var p in params.default) {
                    obj[p] = params.default[p];
                }
            }

            $.extend(scope, ko.mapping.fromJS(obj));

            scope._original = JSON.stringify(obj);
        }
    }

    //process the field value
    Model.prototype.processFields = function () {
        var scope = this;

        scope.fields.forEach(function (field) {
            if (field.isFormField && field.type != 'file') {
                var fieldValue = scope[field.name]();

                if (typeof fieldValue == 'string' && fieldValue.length > 0) {
                    scope[field.name](fieldValue.trim());
                }

                if (field._attr.type == 'number' && field._attr._fixed && fieldValue) {
                    scope[field.name](Number(fieldValue).toFixed(field._attr._fixed));
                }
            }
        });
    };
    //callback(data) when ajax is success
    //forceSave  to save the model when the record is not edit
    Model.prototype.save = function (callback, forceSave) {
        var scope = this;

        if (scope.isDirty() || forceSave) {
            if (scope.checkValidity()) {
                if (scope.preSave()) {
                    var url = Util.generateUrl(scope.proxy);
                    var tempUrl;
                    var tempMethod;

                    if (scope.id() == Config.modelDefaultId || scope.id() == 'confirm') {
                        tempUrl = url;
                        tempMethod = scope.proxy.methods.Post;
                    } else {
                        tempUrl = '{0}/{1}'.formatTpl(url, scope.id());
                        tempMethod = scope.proxy.methods.Put;
                    }

                    if (this.proxy.type == 'ajax') {
                        tempUrl = url;
                    }

                    if (scope.isFormSubmit()) {
                        Util['ajaxForm' + tempMethod]({
                            url: tempUrl, data: this.generateFormData(), callback: function (data) {
                                scope._ajaxCallback(data, callback);
                            }
                        });
                    } else {
                        var jsonObj = ko.mapping.toJS(scope);

                        $.extend(jsonObj, scope.extraData);
                        scope.extraData = {};
                        Util['ajax' + tempMethod]({
                            url: tempUrl, data: jsonObj, callback: function (data) {
                                scope._ajaxCallback(data, callback);
                            }
                        });
                    }
                }
            }
        } else {
            Util.showAlert({data: '请修改表单项', level: 'warning'});
        }
    }

    Model.prototype.delete = function (callback) {
        var scope = this;
        var url = Util.generateUrl(scope.proxy);
        var tempUrl = '{0}/{1}'.formatTpl(url, scope.id());
        var jsonObj = {};

        if (this.proxy.type == 'ajax') {
            tempUrl = url;
            jsonObj = ko.mapping.toJS(scope);
        }

        //the query parameters will add at the end of ajax url
        tempUrl += scope.generateUrlParams();

        Util.ajaxDelete({
            url: tempUrl, data: jsonObj, callback: function (data) {
                scope._ajaxCallback(data, callback);
            }
        });
    }

    Model.prototype._ajaxCallback = function (data, callback) {
        Util.ajaxCallback({data: data, callback: callback, storeId: this._storeId});
    }

    Model.prototype.isDirty = function () {
        this.processFields();

        if (this._original) {
            if (this._original === ko.mapping.toJSON(this)) {
                return false;
            } else {
                return true;
            }
        }

        return false;
    }

    Model.prototype.revert = function () {
        var scope = this;

        if (scope._original) {
            var original = ko.mapping.fromJSON(scope._original);

            scope.fields.forEach(function (field) {
                if (field.name != 'rowId') {
                    switch (field.type) {
                        case 'file':
                            if (scope[field.name].clear) scope[field.name].clear();
                            scope[field.name].fileArray.removeAll();
                            break;
                        case 'shuttle':
                            field.cmp.params._lastItems.removeAll();
                            field.__displayValue(undefined);
                        default:
                            if (original[field.name]) {
                                scope[field.name](original[field.name]());
                            } else {
                                if (scope[field.name]) {
                                    scope[field.name](undefined);
                                }
                            }
                    }
                }
            });
        }
    }

    Model.prototype.getField = function (name) {
        return this.fields.findItemByField({value: name, name: 'name'});
    }

    Model.prototype.isFormSubmit = function () {
        for (var fIndex = 0; fIndex < this.fields.length; ++fIndex) {
            if ('file' == this.fields[fIndex].type) {
                return true;
            }
        }

        return false;
    }

    Model.prototype.checkValidity = function () {
        var scope = this;
        var form = $('#{0}'.formatTpl(scope._formId))[0];

        if (form) {
            //check the file field when form check pass
            //because the h5 validator can't validate the file type and size
            if (form.checkValidity()) {
                for (var fIndex = 0; fIndex < scope.fields.length; ++fIndex) {
                    var field = scope.fields[fIndex];

                    if (field.type == 'file') {
                        if (scope[field.name].file()) {
                            if (field.accept) {
                                var fieldPass = false;
                                var acceptsSplit = field.accept.split(',');

                                for (var aIndex = 0; aIndex < acceptsSplit.length; ++aIndex) {
                                    if (acceptsSplit[aIndex].indexOf('/') == -1) {
                                        if (acceptsSplit[aIndex] == scope[field.name].file().getExtension()) {
                                            fieldPass = true;
                                            break;
                                        }
                                    } else {
                                        var tempSplit = acceptsSplit[aIndex].split('/');
                                        var typeSplit = scope[field.name].file().type.split('/');

                                        if (tempSplit[0] == typeSplit[0]) {
                                            if (tempSplit[1] == '*') {
                                                fieldPass = true;
                                                break;
                                            } else if (tempSplit[1] == typeSplit[1]) {
                                                fieldPass = true;
                                                break;
                                            }
                                        }
                                    }
                                }

                                if (!fieldPass) {
                                    Util.showAlert({
                                        data: '{0} 支持格式 {1}'.formatTpl(field.alias, field.accept),
                                        level: 'error'
                                    });

                                    return false;
                                }
                            }

                            var tempFileSize = 0;

                            scope[field.name].fileArray().forEach(function (file) {
                               tempFileSize += file.size;
                            });

                            if (tempFileSize > field.maxSize) {
                                Util.showAlert({
                                    data: '{0} 超过 {1} MB'.formatTpl(field.alias, (field.maxSize / (1024 * 1024)).toFixed(1)),
                                    level: 'error'
                                });

                                return false;
                            }
                        }
                    }
                }
            } else {
                scope.fields.forEach(function (field) {
                    if (field.isFormField && field._validator && field._validator.length > 0) {
                        var fieldValue;

                        if (field.type == 'file') {
                            fieldValue = scope[field.name].file();
                        } else {
                            fieldValue = scope[field.name] instanceof Function ? scope[field.name]() : undefined;
                        }

                        for (var vIndex = 0; vIndex < field._validator.length; vIndex++) {
                            var pass = true;

                            if (field._validator[vIndex].name == 'required') {
                                if (fieldValue == undefined || fieldValue.toString() == 'NaN' || (typeof fieldValue == 'string' && fieldValue.trim() == '')) {
                                    pass = false;
                                }
                            } else if (field._validator[vIndex].name == 'number') {
                                if (isNaN(Number(fieldValue)) || fieldValue < field._attr.min || fieldValue > field._attr.max) {
                                    pass = false;
                                }
                            } else if (field._validator[vIndex].name == 'email') {
                                if (!RegExp('^([a-zA-Z0-9_\\.\\-])+\\@(([a-zA-Z0-9\\-])+\\.)+([a-zA-Z0-9]{2,4})+$').test(fieldValue)) {
                                    pass = false;
                                }
                            } else {//email mobile idcard
                                if (!RegExp(field._attr.pattern).test(fieldValue)) {
                                    pass = false;
                                }
                            }

                            if (!pass) {
                                field.validateMessage(field._validator[vIndex].message);
                                if (form._firstErrorField == undefined) form._firstErrorField = field;

                                break;
                            }
                        }
                    }
                });

                if (!form.classList.contains('was-validated')) {
                    form.classList.add('was-validated');
                }

                if (form._firstErrorField) {
                    $('#{0}'.formatTpl(form._firstErrorField._attr.id)).focus();
                    form._firstErrorField = undefined;
                }

                Util.showAlert({data: '表单验证失败', level: 'error'});

                return false;
            }
        }

        return true;
    }
    //get json from the fields bind to a form
    Model.prototype.getFormJsonData = function () {
        var scope = this;
        var jsonData = {};

        scope.fields.forEach(function (field) {
            if (field.isFormField) {
                jsonData[field.name] = scope[field.name]();
            }
        });

        return jsonData;
    }
    //generate the form data for ajax
    Model.prototype.generateFormData = function () {
        var formData = new FormData();
        var model = this;

        model.fields.forEach(function (field) {
            if (field.name.substr(0, 1) != '_' && model[field.name]) {
                switch (field.type) {
                    case 'file':
                        if (model[field.name].file && model[field.name].file()) {
                            model[field.name].fileArray().forEach(function (file) {
                                formData.append(field.name, file);
                            });
                        }
                        break;
                    default:
                        if (model[field.name] && model[field.name] instanceof Function) {
                            formData.append(field.name, model[field.name]());
                        }
                }
            }
        });

        for (var key in model.extraData) {
            formData.append(key, model.extraData[key]);
        }

        return formData;
    };
    //check the fields empty
    Model.prototype.allFieldsAssigned = function (fieldNames) {
        var scope = this;

        function rtn(state, fieldAlias) {
            return {success: state, message: fieldAlias + ' 必须填写'};
        }

        for (var fIndex = 0; fIndex < fieldNames.length; ++fIndex) {
            var fieldName = fieldNames[fIndex] instanceof Object ? fieldNames[fIndex].name : fieldNames[fIndex];
            var fieldValue = null;
            var field = scope.getField(fieldName);

            switch (field.type) {
                case 'text':
                case 'date':
                case 'password':
                case 'number':
                case 'textarea':
                    fieldValue = scope[fieldName]();
                    if (!fieldValue) return rtn(false, field.alias);
                    if (fieldValue.length == 0) return rtn(false, field.alias);
                    break;
                case 'checkboxes':
                    fieldValue = scope[fieldName]();
                    if (fieldValue.length == 0) return rtn(false, field.alias);
                    break;
                case 'select':
                    fieldValue = scope[fieldName]();
                    if (!fieldValue) return rtn(false, field.alias);
                    if (fieldValue <= 0) return rtn(false, field.alias);
                    break;
                case 'file':
                    if (scope[fieldName.substr(0, fieldName.length - 1)] &&
                        scope[fieldName.substr(0, fieldName.length - 1)]() &&
                        scope[fieldName.substr(0, fieldName.length - 1)]().length > 0) {
                    } else {
                        fieldValue = scope[fieldName].file();
                        if (!fieldValue) return rtn(false, field.alias);
                    }

                    break;
            }
        }

        return rtn(true);
    };

    Model.prototype.setProxy = function (proxy) {
        $.extend(this.proxy, proxy);
    };

    Model.prototype.getProxy = function () {
        return this.proxy;
    };

    Model.prototype.getStoreId = function () {
        return this._storeId;
    };

    Model.prototype.setStoreId = function (storeId) {
        this._storeId = storeId;
    }
    //convert _query to url parameters when ajaxWidthQuery is true
    //the result will append to the end of the ajax url
    Model.prototype.generateUrlParams = function () {
        var temp = '';

        if (this.ajaxWithQuery) {
            temp = JSON.js2Search(this._query);

            temp = temp ? '?' + temp : '';
        }

        return temp;
    };

    Model.prototype.isViewField = function (fName) {
        var field = this.getField(fName);

        if (field && (field.readonly || field.disabled)) {
            return true;
        } else {
            return false;
        }
    };
    Model.prototype.getFieldParams = function (field) {
        var tempField = field;

        if (typeof tempField == 'string') tempField = this.getField(field);

        var rtn = JSON.parse(tempField._original);

        delete rtn['name'];
        delete rtn['alias'];
        delete rtn['type'];
        delete rtn['validator'];

        return rtn;
    };
    //show form field with layui
    Model.prototype.showForm = function (/*{title:,operation:,skin:,area:,fields:}*/formConfig) {
        var scope = this;

        $.extend(formConfig, scope.formConfig);

        formConfig.operation = formConfig.operation?formConfig.operation:'add';

        switch (formConfig.operation) {
            case 'view':
            case 'add':
            case 'edit':
                var layerConfig = {
                    skin: formConfig.skin ? formConfig.skin : '',
                    title: Config.layerTitleTpl.type_1.formatTpl(formConfig.title),
                    area: formConfig.area ? formConfig.area : ['auto', 'auto'],
                    shadeClose: true,
                    end: function () {
                        scope.fields.forEach(function (field) {
                            if(field.isFormField && field.type == 'date') {
                                $('#{0}'.formatTpl(field._id)).datetimepicker('destroy');
                            }
                        });

                        ko.root.hideModal();
                    }
                };

                if (formConfig.operation != 'view') {
                    $.extend(layerConfig, {
                        shadeClose: false,
                        btn: ['保存'],
                        btnAlign: 'c',
                        yes: function (index) {
                            scope.save(function (data) {
                                if (data.success) {
                                    layer.close(index);
                                }
                            });
                            return false;
                        }
                    });
                }

                ko.root.openPageLayer({
                    modalConfig: {
                        name: 'cmp:UCForm', params: {
                            css: ['p-2','pb-4'],
                            fields: formConfig.fields,
                            hideLabel: formConfig.hideLabel?true:false,
                            cols:formConfig.cols?formConfig.cols:2,
                            record: scope,
                            operation: formConfig.operation
                        }
                    },
                    layerConfig: layerConfig
                });
                break;
            case 'delete':
                layer.confirm('是否删除?', {icon: 3, title: '提示'}, function (index) {
                        scope.delete(function (dataBack) {
                            if(dataBack.success){
                                layer.close(index);
                            }
                        });
                    }
                );
                break;
        }
    };
    return Model;
});
